////////////////////////////////////////////////////////////////////////////////////////////////////
/// Copyright (c) 2009 RacoonStudios 
/// 
/// Permission is hereby granted, free of charge, to any person obtaining a copy of this 
/// software and associated documentation files (the "Software"), to deal in the Software 
/// without restriction, including without limitation the rights to use, copy, modify, merge, 
/// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 
/// to whom the Software is furnished to do so, subject to the following conditions:
/// 
/// The above copyright notice and this permission notice shall be included in all copies or 
/// substantial portions of the Software.
/// 
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
/// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
/// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
/// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
/// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
/// DEALINGS IN THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once
#ifndef byteorder_h__
#define byteorder_h__

#include "core/types.h"
#include "math/float4.h"
#include "math/matrix.h"

namespace System
{
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// \class	ByteOrder
	///
	/// \brief	Byte order. 
	///			Thanks to the NebulaDevice and sorry for just copy&past'n it ...
	///
	/// \author	khaos
	/// \date	07.01.2010
	////////////////////////////////////////////////////////////////////////////////////////////////////
	class ByteOrder
	{
		public:

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \enum	Type
		///
		/// \brief	Values that represent Type. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		enum Type
		{
			LittleEndian = 0,           // e.g. x86
			BigEndian,                  // e.g. PowerPC        
			Network = BigEndian,	///< . 

			#ifdef __WIN32__
			Host = LittleEndian,
			#else
			Host = BigEndian,
			#endif
		};

	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	ByteOrder()
		///
		/// \brief	Default constructor. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		////////////////////////////////////////////////////////////////////////////////////////////////////
		ByteOrder();
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	ByteOrder(Type fromEndianess, Type toEndianess)
		///
		/// \brief	Constructor. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	fromEndianess	from byte order. 
		/// \param	toEndianess		to byte order. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		ByteOrder(Type fromEndianess, Type toEndianess);
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	void SetFromEndianess(Type fromEndianess)
		///
		/// \brief	Sets from byte order. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	fromEndianess	from byte order. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		void SetFromEndianess(Type fromEndianess);
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	Type GetFromEndianess() const
		///
		/// \brief	Gets from byte order. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \return	from byte order. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		Type GetFromEndianess() const;
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	void SetToEndianess(Type toEndianess)
		///
		/// \brief	Sets to byte order. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	toEndianess	to byte order. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		void SetToEndianess(Type toEndianess);
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	Type GetToEndianess() const
		///
		/// \brief	Gets to byte order. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \return	to byte order. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		Type GetToEndianess() const;
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	template<class TYPE> void ConvertInPlace(TYPE& val) const
		///
		/// \brief	Convert in place. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param [in,out]	val	the value. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TYPE> void ConvertInPlace(TYPE& val) const;
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	template<class TYPE> TYPE Convert(TYPE val) const
		///
		/// \brief	Converts. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	val	The value. 
		///
		/// \return	. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TYPE> TYPE Convert(TYPE val) const;
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	template<class TYPE> static void ConvertInPlace(Type fromEndianess, Type toEndianess,
		/// 	TYPE& val)
		///
		/// \brief	Convert in place. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	fromEndianess	from byte order. 
		/// \param	toEndianess		to byte order. 
		/// \param [in,out]	val		the value. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TYPE> static void ConvertInPlace(Type fromEndianess, Type toEndianess, TYPE& val);
	 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// \fn	template<class TYPE> static TYPE Convert(Type fromEndianess, Type toEndianess, TYPE val)
		///
		/// \brief	Converts. 
		///
		/// \author	khaos
		/// \date	07.01.2010
		///
		/// \param	fromEndianess	from byte order. 
		/// \param	toEndianess		to byte order. 
		/// \param	val				The value. 
		///
		/// \return	. 
		////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TYPE> static TYPE Convert(Type fromEndianess, Type toEndianess, TYPE val);    

	private:
		Type from;
		Type to;

		union PunFloatUL
		{
			float f;
			ulong u;
		};
		union PunDoubleULL
		{
			double d;
			unsigned long long u;
		};
	};


	///
	__forceinline
	ByteOrder::ByteOrder() :
		from(Host),
		to(Host)
	{
	 
	}


	///
	__forceinline
	ByteOrder::ByteOrder(ByteOrder::Type fromEndianess, ByteOrder::Type toEndianess) :
		from(fromEndianess),
		to(toEndianess)
	{ 

	}


	///
	__forceinline void
	ByteOrder::SetFromEndianess(Type fromEndianess)
	{
		this->from = fromEndianess;
	}


	///
	__forceinline ByteOrder::Type
	ByteOrder::GetFromEndianess() const
	{
		return this->from;
	}


	///
	__forceinline void
	ByteOrder::SetToEndianess(Type toEndianess)
	{
		this->to = toEndianess;
	}


	///
	__forceinline ByteOrder::Type
	ByteOrder::GetToEndianess() const
	{
		return this->to;
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<short>(Type fromEndianess, Type toEndianess, short& val)
	{
		if (fromEndianess != toEndianess)
		{
			ushort res = _byteswap_ushort((ushort)val);
			val = (short)res;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<short>(short& val) const
	{
		if (this->from != this->to)
		{
			ushort res = _byteswap_ushort((ushort)val);
			val = (short)res;
		}
	}


	///
	template<> __forceinline short
	ByteOrder::Convert<short>(Type fromEndianess, Type toEndianess, short val)
	{
		if (fromEndianess != toEndianess)
		{
			return (short)_byteswap_ushort((ushort)val);        
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline short
	ByteOrder::Convert<short>(short val) const
	{
		if (this->from != this->to)
		{
			return (short)_byteswap_ushort((ushort)val);        
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<ushort>(Type fromEndianess, Type toEndianess, ushort& val)
	{
		if (fromEndianess != toEndianess)
		{
			val = _byteswap_ushort(val);
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<ushort>(ushort& val) const
	{
		if (this->from != this->to)
		{
			val = _byteswap_ushort(val);
		}
	}


	///
	template<> __forceinline ushort
	ByteOrder::Convert<ushort>(Type fromEndianess, Type toEndianess, ushort val)
	{
		if (fromEndianess != toEndianess)
		{
			return _byteswap_ushort(val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline ushort
	ByteOrder::Convert<ushort>(ushort val) const
	{
		if (this->from != this->to)
		{
			return _byteswap_ushort(val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<int>(Type fromEndianess, Type toEndianess, int& val)
	{
		if (fromEndianess != toEndianess)
		{
			uint res = _byteswap_ulong((uint)val);
			val = (int)res;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<int>(int& val) const
	{
		if (this->from != this->to)
		{
			uint res = _byteswap_ulong((uint)val);
			val = (int)res;
		}
	}


	///
	template<> __forceinline int
	ByteOrder::Convert<int>(Type fromEndianess, Type toEndianess, int val)
	{
		if (fromEndianess != toEndianess)
		{
			return (int) _byteswap_ulong((uint)val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline int
	ByteOrder::Convert<int>(int val) const
	{
		if (this->from != this->to)
		{
			return (int) _byteswap_ulong((uint)val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<uint>(Type fromEndianess, Type toEndianess, uint& val)
	{
		if (fromEndianess != toEndianess)
		{
			val = _byteswap_ulong(val);
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<uint>(uint& val) const
	{
		if (this->from != this->to)
		{
			val = _byteswap_ulong(val);
		}
	}


	///
	template<> __forceinline uint
	ByteOrder::Convert<uint>(Type fromEndianess, Type toEndianess, uint val)
	{
		if (fromEndianess != toEndianess)
		{
			return _byteswap_ulong(val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline uint
	ByteOrder::Convert<uint>(uint val) const
	{
		if (this->from != this->to)
		{
			return _byteswap_ulong(val);
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<float>(Type fromEndianess, Type toEndianess, float& val)
	{
		if (fromEndianess != toEndianess)
		{
			PunFloatUL pun;
			pun.f = val;
			pun.u = _byteswap_ulong(pun.u);
			val = pun.f;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<float>(float& val) const
	{
		if (this->from != this->to)
		{
			PunFloatUL pun;
			pun.f = val;
			pun.u = _byteswap_ulong(pun.u);
			val = pun.f;
		}
	}


	///
	template<> __forceinline float
	ByteOrder::Convert<float>(Type fromEndianess, Type toEndianess, float val)
	{
		if (fromEndianess != toEndianess)
		{
			PunFloatUL pun;
			pun.f = val;
			pun.u = _byteswap_ulong(pun.u);
			return pun.f;
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline float
	ByteOrder::Convert<float>(float val) const
	{
		if (this->from != this->to)
		{
			PunFloatUL pun;
			pun.f = val;
			pun.u = _byteswap_ulong(pun.u);
			return pun.f;
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<double>(Type fromEndianess, Type toEndianess, double& val)
	{
		if (fromEndianess != toEndianess)
		{
			PunDoubleULL pun;
			pun.d = val;
			pun.u = _byteswap_uint64(pun.u);
			val = pun.d;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<double>(double& val) const
	{
		if (this->from != this->to)
		{
			PunDoubleULL pun;
			pun.d = val;
			pun.u = _byteswap_uint64(pun.u);
			val = pun.d;
		}
	}


	///
	template<> __forceinline double
	ByteOrder::Convert<double>(Type fromEndianess, Type toEndianess, double val)
	{
		if (fromEndianess != toEndianess)
		{
			PunDoubleULL pun;
			pun.d = val;
			pun.u = _byteswap_uint64(pun.u);
			return pun.d;
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline double
	ByteOrder::Convert<double>(double val) const
	{
		if (this->from != this->to)
		{
			PunDoubleULL pun;
			pun.d = val;
			pun.u = _byteswap_uint64(pun.u);
			return pun.d;
		}
		else
		{
			return val;
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<Math::float4>(Type fromEndianess, Type toEndianess, Math::float4& val)
	{
		if (fromEndianess != toEndianess)
		{
			ConvertInPlace<float>(fromEndianess, toEndianess, float(val.getx()));
			ConvertInPlace<float>(fromEndianess, toEndianess, float(val.gety()));
			ConvertInPlace<float>(fromEndianess, toEndianess, float(val.getz()));
			ConvertInPlace<float>(fromEndianess, toEndianess, float(val.getw()));
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<Math::float4>(Math::float4& val) const
	{
		if (this->from != this->to)
		{
			ConvertInPlace<float>(float(val.getx()));
			ConvertInPlace<float>(float(val.gety()));
			ConvertInPlace<float>(float(val.getz()));
			ConvertInPlace<float>(float(val.getw()));
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<Math::matrix>(Type fromEndianess, Type toEndianess, Math::matrix& val)
	{
		if (fromEndianess != toEndianess)
		{
			Math::float4 row0 = val.getr1();
			Math::float4 row1 = val.getr2();
			Math::float4 row2 = val.getr3();
			Math::float4 row3 = val.getr4();
			ConvertInPlace<Math::float4>(fromEndianess, toEndianess, row0);
			ConvertInPlace<Math::float4>(fromEndianess, toEndianess, row1);
			ConvertInPlace<Math::float4>(fromEndianess, toEndianess, row2);
			ConvertInPlace<Math::float4>(fromEndianess, toEndianess, row3);
			val.setr1(row0);
			val.setr2(row1);
			val.setr3(row2);
			val.setr4(row3);
		}
	}


	///
	template<> __forceinline void
	ByteOrder::ConvertInPlace<Math::matrix>(Math::matrix& val) const
	{
		if (this->from != this->to)
		{
			Math::float4 row0 = val.getr1();
			Math::float4 row1 = val.getr2();
			Math::float4 row2 = val.getr3();
			Math::float4 row3 = val.getr4();
			ConvertInPlace<Math::float4>(row0);
			ConvertInPlace<Math::float4>(row1);
			ConvertInPlace<Math::float4>(row2);
			ConvertInPlace<Math::float4>(row3);
			val.setr1(row0);
			val.setr2(row1);
			val.setr3(row2);
			val.setr4(row3);
		}
	}

}

#endif // byteorder_h__